<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<script type="text/javascript">
			ES6 之前，不能直接为函数的参数指定默认值
			ES6 允许为函数的参数设置默认值，即直接写在参数定义的后面
			function log(x, y = 'World') {
			  console.log(x, y);
			}
			log('Hello') // Hello World
			log('Hello', 'China') // Hello China
			log('Hello', '') // Hello
			
			参数变量是默认声明的，所以不能用let或const再次声明
			function foo(x = 5) {
			  let x = 1; // error
			  const x = 2; // error
			}
			
			使用参数默认值时，函数不能有同名参数。
			// 不报错
			function foo(x, x, y) {
			  // ...
			}
			
			// 报错
			function foo(x, x, y = 1) {
			  // ...
			}
			// SyntaxError: Duplicate parameter name not allowed in this context
			
			
			与解构赋值默认值结合使用
			function foo({x, y = 5}) {
			  console.log(x, y);
			}
			
			foo({}) // undefined 5
			foo({x: 1}) // 1 5
			foo({x: 1, y: 2}) // 1 2
			foo() // TypeError: Cannot read property 'x' of undefined
			
			
			
			作用域
			一旦设置了参数的默认值，
			函数进行声明初始化时，参数会形成一个单独
			的作用域（context）。等到初始化结束，这个作用域就会
			消失。这种语法行为，在不设置参数默认值时，是不会出现的
			let x = 1;
			function f(x, y = x) {
			  console.log(y);
			}
			f(2) // 2
			
			let x = 1;
			function f(y = x) {
			  let x = 2;
			  console.log(y);
			}
			f() // 1
			
			
			如果此时，全局变量x不存在，就会报错。
			function n(y=x) {
			  let x = 2;
			  console.log(y);
			}
			n()// ReferenceError: x is not defined
			
			
			下面代码中，参数x = x形成一个单独作用域。实际执行的
			是let x = x，由于暂时性死区的原因，这行代码会报错”x 未定义“,
			let x = 1;
			function foo(x = x) {
			  // ...
			}
			foo() // ReferenceError: x is not defined
			
			
			如果参数的默认值是一个函数，该函数的作用域也遵守这个规则
			let foo = 'outer';
			function bar(func = () => foo) {
			  let foo = 'inner';
			  console.log(func());
			}
			bar(); // outer
			
			
			
			//rest 参数
			est 参数（形式为...变量名），用于获取函数的多余参数，
			这样就不需要使用arguments对象了。rest 参数搭配的变
			量是一个数组，该变量将多余的参数放入数组中
			function add(...values) {
			  let sum = 0;
			  for (let val of values) {
			    sum += val;
			  }
			  return sum;
			}
			add(2, 5, 3) // 10
			
			// arguments变量的写法
			function sortNumbers() {
			  return Array.prototype.slice.call(arguments).sort();
			}
			// rest参数的写法
			const sortNumbers = (...numbers) => numbers.sort();
			rguments对象不是数组，而是一个类似数组的对象。所以为了使用
			数组的方法，必须使用Array.prototype.slice.call先将其转为
			数组。rest 参数就不存在这个问题，它就是一个真正的数组，数组
			特有的方法都可以使用.
			function push(array, ...items) {
			  items.forEach(function(item) {
			    array.push(item);
			    console.log(item);
			  });
			}
			let a = [];
			push(a, 1, 2, 3)
			注意
			注意，rest 参数之后不能再有其他参数（即只能是最后一个参数），否则会报错
			// 报错
			function f(a, ...b, c) {
			  // ...
			}
			
			只要函数参数使用了默认值、解构赋值、或者扩展运算符，
			那么函数内部就不能显式设定为严格模式，否则会报错。
			函数内部的严格模式，同时适用于函数体和函数参数。但是，
			函数执行的时候，先执行函数参数，然后再执行函数体。
			这样就有一个不合理的地方，只有从函数体之中，
			才能知道参数是否应该以严格模式执行，但是参数却应该先于函数体执行。
			
			
			
			箭头函数
			let f = v => v;
			// 等同于
			let f = function (v) {
			  return v;
			};
			
			let f = () => 5;
			// 等同于
			let f = function () { return 5 };
			
			
			let sum = (num1, num2) => num1 + num2;
			// 等同于
			let sum = function(num1, num2) {
			  return num1 + num2;
			};
			
			如果箭头函数的代码块部分多于一条语句，就要使用大括号
			将它们括起来，并且使用return语句返回
			var sum = (num1, num2) => { return num1 + num2; }
			
			
			由于大括号被解释为代码块，所以如果箭头函数直接返回一个对
			象，必须在对象外面加上括号，否则会报错
			// 报错
			let getTempItem = id => { id: id, name: "Temp" };
			// 不报错
			let getTempItem = id => ({ id: id, name: "Temp" });
			
			
			箭头函数可以与变量解构结合使用。
			const full = ({ first, last }) => first + ' ' + last;
			// 等同于
			function full(person) {
			  return person.first + ' ' + person.last;
			}
			
			箭头函数使得表达更加简洁。
			const isEven = n => n % 2 === 0;
			const square = n => n * n;
			
			 rest 参数与箭头函数结合的例子。
			 const numbers = (...nums) => nums;
			numbers(1, 2, 3, 4, 5)
			// [1,2,3,4,5]

			
			使用注意点
			箭头函数有几个使用注意点。
			（1）函数体内的this对象，就是定义时所在的对象，
				而不是使用时所在的对象。
			（2）不可以当作构造函数，也就是说，不可以使
				用new命令，否则会抛出一个错误。
			（3）不可以使用arguments对象，该对象在函数体内不
				存在。如果要用，可以用 rest 参数代替。
			（4）不可以使用yield命令，因此箭头函数不能
				用作 Generator 函数。
			上面四点中，第一点尤其值得注意。this对象的指向
			是可变的，但是在箭头函数中，它是固定的。
			即普通函数this的指向是指向运行时所在作用域的,一般是全局作用域,
			箭头函数this的指向是指向定义时作用域的,一般是某个对象或方法的作用域
			所有有时在vue箭头函数用this取别的方法或变量是取不到的,
			因为此时他的this的指向只是指向他所在的那个方法或者对象
			
			
			
			
			
			var handler = {
			  id: '123456',
			  init: function() {
			    document.addEventListener('click',
			      event => this.doSomething(event.type), false);
			  },
			  //箭头函数根本没有自己的this，导致内部的this就是外层代码块
			  //的this，此时外层代码块的this指向的是全局，所以此时的需要用
			  //全局的语法取doSomething方法才不会报错
			  arrowFunction:()=>handler.doSomething('定海神针'),
			  doSomething: function(type) {
			    console.log('Handling ' + type  + ' for ' + this.id);
			  }
			};
			上面代码的init方法中，使用了箭头函数，这导致这个箭头函数里面
			的this，总是指向handler对象。否则，回调函数运行时，
			this.doSomething这一行会报错，因为此时this指向document对象。
			this指向的固定化，并不是因为箭头函数内部有绑定this的机制，实际原因
			是箭头函数根本没有自己的this，导致内部的this就是外层代码块
			的this。正是因为它没有this，所以也就不能用作构造函数。
			
			
			由于箭头函数没有自己的this，所以当然也就不能用call()、
			apply()、bind()这些方法去改变this的指向
			function foo() {
			  return () => {
			    return () => {
			      return () => {
			        console.log('id:', this.id);
			      };
			    };
			  };
			}
			var f = foo.call({id: 1});
			var t1 = f.call({id: 2})()(); // id: 1
			var t2 = f().call({id: 3})(); // id: 1
			var t3 = f()().call({id: 4}); // id: 1
			上面代码之中，只有一个this，就是函数foo的this，所
			以t1、t2、t3都输出同样的结果。因为所有的内层函数
			都是箭头函数，都没有自己的this，它们的this其实
			都是最外层foo函数的this。
			
			
			
			不适用场合
			由于箭头函数使得this从“动态”变成“静态”，
			下面两个场合不应该使用箭头函数。
			第一个场合是定义对象的方法，且该方法内部包括this。
			const cat = {
			  lives: 9,
			  jumps: () => {
			    this.lives--;
			  }
			}
			上面代码中，cat.jumps()方法是一个箭头函数，这是错误的。
			调用cat.jumps()时，如果是普通函数，该方法内部的this指向cat；
			如果写成上面那样的箭头函数，使得this指向全局对象，因
			此不会得到预期结果。这是因为对象不构成单独的作用域，
			导致jumps箭头函数定义时的作用域就是全局作用域。
			
			globalThis.s = 21;
			const obj = {
			  s: 42,
			  m: () => console.log(this.s)
			};
			
			obj.m() // 21
			上面例子中，obj.m()使用箭头函数定义。JavaScript 引擎的
			处理方法是，先在全局空间生成这个箭头函数，然后赋值
			给obj.m，这导致箭头函数内部的this指向全局对象，所
			以obj.m()输出的是全局空间的21，而不是对象内部的42。
			上面的代码实际上等同于下面的代码。
			globalThis.s = 21;
			globalThis.m = () => console.log(this.s);
			
			const obj = {
			  s: 42,
			  m: globalThis.m
			};
			
			obj.m() // 21
			
			
			第二个场合是需要动态this的时候，也不应使用箭头函数。
			var button = document.getElementById('press');
			button.addEventListener('click', () => {
			  this.classList.toggle('on');
			});
			上面代码运行时，点击按钮会报错，因为button的监听函数是一个箭头函
			数，导致里面的this就是全局对象。如果改成普通函
			数，this就会动态指向被点击的按钮对象。
			
			
			普通函数的this指向他的父级作用域,一般是调用他的那个对象的作用域
			箭头函数的this就是他的父级的this
			
			
			Function.prototype.toString()
			toString()方法返回函数代码本身，
			function /* foo comment */ foo () {}
			foo.toString()
			// "function /* foo comment */ foo () {}"
			
			
			catch 命令的参数省略
			JavaScript 语言的try...catch结构，以前明确要求catch命
			令后面必须跟参数，接受try代码块抛出的错误对象。
			try {
			  // ...
			} catch (err) {
			  // 处理错误
			}
			ES2019 做出了改变，允许catch语句省略参数
			try {
			  // ...
			} catch {
			  // ...
			}
		</script>
	</body>
</html>
